Appearance
[中文版本](/posts/c教程/zh-CN/08-调试测试与 profiling/075-Logging Systems)
As your programs grow, printf debugging quickly becomes messy. You need a way to see inside your program, what it’s doing, what went wrong, and why, without flooding your terminal with random messages.
That’s where logging systems come in. A good log system helps you trace execution, record errors, and understand how your program behaves over time.
Step 1. What Is Logging?
Logging is like keeping a diary for your program. Instead of printing everything to the screen, you log structured messages with levels (INFO, WARN, ERROR) and timestamps.
It’s essential for:
- Debugging complex flows.
- Auditing events and errors.
- Monitoring long-running services.
- Diagnosing crashes and performance issues.
Step 2. The Simplest Logger, printf with Context
#include <stdio.h>
int main(void) {
printf("[INFO] Starting program\n");
printf("[WARN] Low memory\n");
printf("[ERROR] Failed to open file\n");
}This works, but there’s no timestamp, file name, or severity control.
Step 3. Adding Log Levels and Macros
We can make this structured and reusable using macros.
#include <stdio.h>
#include <time.h>
#define LOG(level, msg, ...) do { \
time_t t = time(NULL); \
struct tm *tm_info = localtime(&t); \
char buf[20]; \
strftime(buf, 20, "%Y-%m-%d %H:%M:%S", tm_info); \
fprintf(stderr, "[%s] [%s] " msg "\n", buf, level, ##__VA_ARGS__); \
} while (0)
#define LOG_INFO(msg, ...) LOG("INFO", msg, ##__VA_ARGS__)
#define LOG_WARN(msg, ...) LOG("WARN", msg, ##__VA_ARGS__)
#define LOG_ERROR(msg, ...) LOG("ERROR", msg, ##__VA_ARGS__)
int main(void) {
LOG_INFO("Program started");
LOG_WARN("Memory usage at %d%%", 80);
LOG_ERROR("File %s not found", "data.txt");
}Output:
[2025-10-16 23:41:09] [INFO] Program started
[2025-10-16 23:41:09] [WARN] Memory usage at 80%
[2025-10-16 23:41:09] [ERROR] File data.txt not foundNow your logs have consistent structure and useful context.
Step 4. Controlling Log Verbosity
Add a global log level:
enum { LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERROR };
int CURRENT_LOG_LEVEL = LOG_LEVEL_INFO;
#define SHOULD_LOG(level) ((level) >= CURRENT_LOG_LEVEL)
#define LOGX(level, tag, msg, ...) do { \
if (SHOULD_LOG(level)) { \
time_t t = time(NULL); \
struct tm *tm_info = localtime(&t); \
char buf[20]; \
strftime(buf, 20, "%H:%M:%S", tm_info); \
fprintf(stderr, "[%s] [%s] " msg "\n", buf, tag, ##__VA_ARGS__); \
} \
} while (0)Now:
LOGX(LOG_LEVEL_INFO, "INFO", "Running");
LOGX(LOG_LEVEL_WARN, "WARN", "Low disk space");
LOGX(LOG_LEVEL_ERROR, "ERROR", "Crash at line %d", __LINE__);Change verbosity dynamically:
CURRENT_LOG_LEVEL = LOG_LEVEL_WARN;Now INFO messages are skipped.
Step 5. Logging to a File
#include <stdio.h>
#include <time.h>
void log_to_file(const char *filename, const char *msg) {
FILE *f = fopen(filename, "a");
if (!f) return;
time_t t = time(NULL);
fprintf(f, "%s: %s\n", ctime(&t), msg);
fclose(f);
}
int main(void) {
log_to_file("log.txt", "Program started");
log_to_file("log.txt", "Action complete");
}The log file will contain:
Thu Oct 16 23:41:09 2025: Program started
Thu Oct 16 23:41:10 2025: Action completeStep 6. Including File and Line Information
You can include source info automatically using__FILE__ and__LINE__:
#define LOG_DEBUG(msg, ...) \
fprintf(stderr, "[DEBUG] %s:%d " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__)Example:
LOG_DEBUG("x = %d", x);Output:
[DEBUG] main.c:42 x = 10Step 7. Rotating or Limiting Logs
For long-running programs, you don’t want logs to grow forever. You can:
- Truncate or rename old files.
- Only keep N entries.
- Write daily logs (
log_2025-10-16.txt).
Example:
char filename[64];
time_t now = time(NULL);
strftime(filename, sizeof(filename), "log_%Y-%m-%d.txt", localtime(&now));
log_to_file(filename, "Daily entry");Step 8. Adding Colors (Optional)
Make console logs easier to read with ANSI color codes:
#define RED "\x1b[31m"
#define YEL "\x1b[33m"
#define GRN "\x1b[32m"
#define RST "\x1b[0m"
#define LOGC(level, color, msg, ...) \
fprintf(stderr, color "[%s] " msg RST "\n", level, ##__VA_ARGS__)Example:
LOGC("INFO", GRN, "Server started");
LOGC("WARN", YEL, "High CPU usage");
LOGC("ERROR", RED, "Out of memory");Step 9. Combining Logging and Assertions
You can combine assertions with logs for extra safety:
#include <assert.h>
#define SAFE_LOG(cond, msg, ...) \
if (!(cond)) { \
LOG_ERROR(msg, ##__VA_ARGS__); \
assert(cond); \
}If something fails, it both logs and triggers an assertion.
Step 10. Tiny Code: Minimal Logger
#include <stdio.h>
#include <time.h>
#define LOG(fmt, ...) do { \
time_t now = time(NULL); \
char buf[20]; \
strftime(buf, sizeof(buf), "%H:%M:%S", localtime(&now)); \
fprintf(stderr, "[%s] " fmt "\n", buf, ##__VA_ARGS__); \
} while (0)
int main(void) {
LOG("Starting program");
LOG("Loading config");
LOG("Finished setup");
}Output:
[23:42:00] Starting program
[23:42:01] Loading config
[23:42:02] Finished setupWhy It Matters
Logging makes invisible processes visible. It helps you:
- Trace execution flow.
- Debug production code.
- Audit errors and warnings.
- Understand system performance over time.
In real systems, servers, compilers, databases, logs are your lifeline when things go wrong.
Try It Yourself
- Add
LOG_INFO,LOG_WARN, andLOG_ERRORmacros to one of your C projects. - Write logs to both
stderrand a file. - Add timestamps and line numbers automatically.
- Add colors for each level.
- Implement a rotating file log system that keeps only today’s file active.
Next, you’ll learn about profiling with gprof, how to measure where your program spends its time, and how to make it faster.
